package com.j256.ormlite.hmos;

import com.j256.ormlite.dao.ObjectCache;
import com.j256.ormlite.field.SqlType;
import com.j256.ormlite.logger.Logger;
import com.j256.ormlite.logger.LoggerFactory;
import com.j256.ormlite.stmt.StatementBuilder.StatementType;
import com.j256.ormlite.support.CompiledStatement;
import com.j256.ormlite.support.DatabaseResults;
import ohos.data.rdb.RdbStore;
import ohos.data.rdb.Statement;
import ohos.data.resultset.ResultSet;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * HmosCompiledStatement
 *
 */
public class HmosCompiledStatement implements CompiledStatement {
	private static Logger logger = LoggerFactory.getLogger(HmosCompiledStatement.class);
	private static final String[] NO_STRING_ARGS = new String[0];
	private final String sql;
    private final RdbStore db;
    private final StatementType type;
    private final boolean isCancelQueriesEnabled;
    private final boolean isCacheStore;

    private ResultSet cursor;
    private List<Object> args;
    private Integer max;

    /**
     * writeConfigFile
     *
     * @param sql config file
     * @param db Database
     * @param type Type
     * @param isCancelQueriesEnabled CancelQueries
     * @param isCacheStore Boolean
     */
    public HmosCompiledStatement(String sql, RdbStore db, StatementType type, boolean isCancelQueriesEnabled,
                                 boolean isCacheStore) {
        this.sql = sql;
        this.db = db;
        this.type = type;
        this.isCancelQueriesEnabled = isCancelQueriesEnabled;
        this.isCacheStore = isCacheStore;
    }

    @Override
    public int getColumnCount() throws SQLException {
        return 0;
    }

    @Override
    public String getColumnName(int column) throws SQLException {
        return null;
    }

    @Override
    public DatabaseResults runQuery(ObjectCache objectCache) throws SQLException {
        if (!type.isOkForQuery()) {
            throw new IllegalArgumentException("Cannot call" + type + "testing");
        }
        return new HmosDatabaseResults(getCursor(), objectCache, isCacheStore);
    }

    @Override
    public int runUpdate() throws SQLException {
        if (!type.isOkForUpdate()) {
            throw new IllegalArgumentException("Cannot call " + type + " statement");
        }
        String finalSql;
        if (max == null) {
            finalSql = sql;
        } else {
            finalSql = sql + " " + max;
        }
        return execSql(db, "runUpdate", finalSql, getArgArray());
    }

    @Override
    public int runExecute() throws SQLException {
        if (!type.isOkForExecute()) {
            throw new IllegalArgumentException("Cannot call " + type + " statement");
        }
        return execSql(db, "runExecute", sql, getArgArray());
    }

    @Override
    public void close() throws IOException {
    }

    @Override
    public void closeQuietly() {
    }

    @Override
    public void cancel() {
    }

    @Override
    public void setObject(int parameterIndex, Object obj, SqlType sqlType) throws SQLException {
        isInPrep();
        if (args == null) {
            args = new ArrayList<Object>();
        }
        if (obj == null) {
            args.add(parameterIndex, null);
            return;
        }

        switch (sqlType) {
            case STRING:
            case LONG_STRING:
            case DATE:
            case BOOLEAN:
            case CHAR:
            case BYTE:
            case SHORT:
            case INTEGER:
            case LONG:
            case FLOAT:
            case DOUBLE:
                args.add(parameterIndex, obj.toString());
                break;
            case BYTE_ARRAY:
            case SERIALIZABLE:
                args.add(parameterIndex, obj);
                break;
            case BLOB:
            case BIG_DECIMAL:
                throw new SQLException("Invalid type: " + sqlType);
            case UNKNOWN:
            default:
                throw new SQLException("Unknown sql argument type: " + sqlType);
        }
    }

    @Override
    public void setMaxRows(int imax) throws SQLException {
        isInPrep();
        this.max = imax;
    }

    @Override
    public void setQueryTimeout(long millis) {
        // as far as I could tell this is not supported by API
    }

    @Override
    public String getStatement() {
        return sql;
    }

    /***
     * This is mostly an internal class but is exposed for those people who need access to the Cursor itself.
     *
     * @return cursor Cursor
     * @throws SQLException Exception
     */
    public ResultSet getCursor() throws SQLException {
        if (cursor == null) {
            String finalSql = null;
            if (max == null) {
                finalSql = sql;
            } else {
                finalSql = sql + " LIMIT " + max;
            }

            cursor = db.querySql(finalSql, getStringArray());
            cursor.goToFirstRow();
            logger.trace("{}: started rawQuery cursor for: {}", this, finalSql);
        }

        return cursor;
    }

    @Override
    public String toString() {
        return sql;
    }

    /**
     * Execute some SQL on the database and return the number of rows changed.
     *
     * @param argArray object
     * @param db Database
     * @param finalSql Sqlroot
     * @param label Label
     * @throws SQLException
     * @return int
     */
    static int execSql(RdbStore db, String label, String finalSql, Object[] argArray) throws SQLException {
        db.executeSql(finalSql, argArray);
        int result;
        Statement stmt = null;
        try {
            stmt = db.buildStatement("SELECT CHANGES()");
            result = (int) stmt.executeAndGetLong();
        } finally {
            if (stmt != null) {
                stmt.close();
            }
        }
        logger.trace("executing statement {} changed {} rows: {}", label, result, finalSql);
        return result;
    }

    private void isInPrep() throws SQLException {
    }

    private Object[] getArgArray() {
        if (args == null) {
            return NO_STRING_ARGS;
        } else {
            return args.toArray(new Object[args.size()]);
        }
    }

    private String[] getStringArray() {
        if (args == null) {
            return NO_STRING_ARGS;
        } else {
            return args.toArray(new String[args.size()]);
        }
    }
}
